//
// Created by Moonbreeze on 2015/12/17.
//
#include "JavaClass.h"
#include "ClassHeap.h"
#include "ObjectHeap.h"
#include <fstream>
#include <iostream>
#include <assert.h>
#include <string.h>

JavaClass::JavaClass()
:m_pClassHeap(NULL),
 m_pByteCode(NULL),
 m_nByteCodeLength(0),
 m_nObjectFieldsCount(0)
{ }

JavaClass::~JavaClass() {
    if (m_pByteCode) delete m_pByteCode;
}

bool JavaClass::LoadClassFormFile(std::string FilePath) {
    char* p;
    size_t lenRead,len = 0;
    std::ifstream getLen(FilePath.c_str(),std::ios::ate);
    if (getLen.is_open()){
        len = getLen.tellg();
        getLen.close();
    }
    if (len == 0){
        return false;
    }
    std::ifstream classFileStream(FilePath.c_str(),std::ios::in | std::ios::binary);

    p = new char[len + 2];
    if (classFileStream.is_open()){
        classFileStream.read(p,len);
        lenRead = classFileStream.gcount();
        if (lenRead != len){
            delete p;
            p = NULL;
            return false;
        }

        classFileStream.close();
        if (p){
            p[len] = 0;
            m_nByteCodeLength = len;
            SetByteCode(p);
        }
    }else{
        return false;
    }
    return true;
}

void JavaClass::SetByteCode(void *pByteCode) {
    if(m_pByteCode) delete m_pByteCode;
    m_pByteCode = (char*)pByteCode;

    if (m_pByteCode) ParseClass();
}

bool JavaClass::ParseClass() {
    if (m_pByteCode == NULL || m_nByteCodeLength < sizeof(JavaClassFileFormat) + 20)
        return false;

    char* p = m_pByteCode;
    magic = getu4(p); p += 4;
    /*
     * check the magic value
     */
    if (magic != 0xCAFEBABE)
        return false;
    /*
     * load the version info
     */
    minor_version = getu2(p); p += 2;
    major_version = getu2(p); p += 2;
    /*
     * load and build constant pool
     */
    constant_pool_count = getu2(p); p+= 2;
    if(constant_pool_count > 0){
        ParseConstantPool(p);
    }
    //access_flags
    access_flags = getu2(p); p += 2;
    /*
     * class info
     */
    this_class = getu2(p); p += 2;
    super_class = getu2(p); p += 2;
    /*
     * load and build interfaces
     */
    interfaces_count = getu2(p); p += 2;
    if(interfaces_count > 0){
        ParseInterfaces(p);
    }
    /*
     * load field
     */
    fields_count = getu2(p); p += 2;
    if (fields_count > 0){
        ParseFields(p);
    }
    /*
     * load method
     */
    methods_count = getu2(p); p += 2;
    if (methods_count > 0){
        ParseMethods(p);
    }
    /*
     * load attributes
     */
    attributes_count = getu2(p); p += 2;
    if (attributes_count > 0){
        ParseAttributes(p);
    }
    return true;
}

bool JavaClass::ParseAttributes(char *&p) {
    attributes = new Attribute_info*[attributes_count];
    if (methods == NULL) return false;

    int i;
    for(i = 0; i < attributes_count;i++){
        attributes[i] = (Attribute_info*)p;
        u2 name_index = getu2(p);p += 2;
        u4 len = getu4(p); p += 4;
        p += len;
    }
    return true;
}

bool JavaClass::ParseMethods(char *&p) {
    methods = new Method_info_ex[methods_count];
    if (methods == NULL) return false;

    for (int i = 0; i < methods_count; ++i) {
        methods[i].pMethodInfoBase = (Method_info*)p;
        methods[i].access_flags = getu2(p); p += 2;
        methods[i].name_index = getu2(p); p += 2;
        methods[i].descriptor_index = getu2(p); p += 2;
        methods[i].attributes_count = getu2(p); p += 2;

        std::string strName,strDesc;
        GetStringFormConstPool(methods[i].name_index,strName);
        GetStringFormConstPool(methods[i].descriptor_index,strDesc);
        methods[i].pCode_attr = NULL;
        if (methods[i].attributes_count > 0){
            for (int j = 0; j < methods[i].attributes_count; ++j) {
                u2 name_index = getu2(p); p += 2;
                u4 len = getu4(p); p+= 4;
                p += len;
            }
            methods[i].pCode_attr = new Code_attribute();
            ParseMethodCodeAttribute(i,methods[i].pCode_attr);
        }
    }
    return true;
}

bool JavaClass::ParseConstantPool(char *&p) {
    constant_pool = new CONSTANT_POOL_info*[constant_pool_count];
    
    if(constant_pool == NULL) return false;
    
    
    for(int i=1;i<constant_pool_count;i++)
    {
        constant_pool[i]=(CONSTANT_POOL_info*)p;
        
        int size = GetConstantPoolSize(p);
        p+= size;
        
        //printf("Constant pool %d type %d\n",i,(u1)constant_pool[i]->tag);
        
        if(constant_pool[i]->tag == CONSTANT_Long || constant_pool[i]->tag == CONSTANT_Double)
        {
            constant_pool[i+1]=NULL;
            i++;
        }
    }
    
    return true;
}

bool JavaClass::ParseFields(char *&p) {
    fields = new Field_info_ex[fields_count];
    if (fields == NULL) return false;

    for (int i = 0; i < fields_count; ++i) {
        fields[i].access_flags = getu2(p); p += 2;
        fields[i].name_index = getu2(p); p += 2;
        fields[i].descriptor_index = getu2(p); p += 2;
        fields->attributes_count = getu2(p);p += 2;

        if (fields[i].attributes_count > 0){
            for (int j = 0; j < fields[i].attributes_count; ++j) {
                u2 name_index = getu2(p); p += 2;
                u4 len = getu4(p); p += 4;
                p += len;
            }
        }
    }
    return true;
}

bool JavaClass::ParseInterfaces(char *&p) {
    interfaces = new u2[interfaces_count];
    for (int i = 0; i < interfaces_count; ++i) {
        interfaces[i] = getu2(p); p += 2;
    }
    return true;
}

int JavaClass::GetConstantPoolSize(char *p) {
    CONSTANT_POOL_info* constant_pool_info = (CONSTANT_POOL_info*)p;
    switch (constant_pool_info->tag){
        case CONSTANT_Class:
            return 3;
        case CONSTANT_Fieldref:
            return 5;
        case CONSTANT_Methodref:
            return 5;
        case CONSTANT_InterfaceMethodref:
            return 5;
        case CONSTANT_String:
            return 3;
        case CONSTANT_Integer:
            return 5;
        case CONSTANT_Float:
            return 5;
        case CONSTANT_Long:
            return 9;
        case CONSTANT_Double:
            return 9;
        case CONSTANT_NameAndType:
            return 5;
        case CONSTANT_Utf8:
            return 3 + getu2(p+1);
        default:
            assert(false);
            break;
    }
}

bool JavaClass::GetConstantPool(u2 index, CONSTANT_POOL_info &const_pool) {
    if (index > constant_pool_count - 1) return false;

    char* cpool = (char*) constant_pool[index];
    const_pool.tag = cpool[0]; cpool++;
    if(CONSTANT_Utf8 == const_pool.tag){
        int len = getu2(cpool); cpool += 2;
        const_pool.info = new u1[len + 1];
        memset(const_pool.info,0,len+1);
        memcpy(const_pool.info,cpool,len);
    }

    if (CONSTANT_Class == const_pool.tag){
        const_pool.info = new u1[2];
        memcpy(const_pool.info,cpool,2);
    }

    return true;
}

bool JavaClass::GetStringFormConstPool(int index, std::string &strValue) {
    if (index < 1 || index >= constant_pool_count) return false;

    if (constant_pool[index]->tag != CONSTANT_Utf8) return false;

    u1* p = (u1*) constant_pool[index];

    u2 length = getu2(&p[1]);
    u1* buffer = new u1[length+1];
    buffer[length] = 0;
    memcpy(buffer,&p[3],length);
    strValue = strValue + (const char*)buffer;
    delete buffer;

    return true;
}

std::string JavaClass::GetName() {
    std::string retVal;
    if (constant_pool[this_class]->tag != CONSTANT_Class)
        return retVal;

    char* temp = (char*)constant_pool[this_class];
    u2 name_index = getu2(&temp[1]);
    GetStringFormConstPool(name_index,retVal);
    return retVal;
}

std::string JavaClass::GetSuperClassName() {
    std::string retVal;
    if (super_class < 1) return retVal;

    if (constant_pool[super_class]->tag != CONSTANT_Class)
        return retVal;

    char* temp = (char*)constant_pool[super_class];
    u2 name_index = getu2(&temp[1]);
    GetStringFormConstPool(name_index,retVal);
    return retVal;
}

bool JavaClass::ParseMethodCodeAttribute(int methodIndex, Code_attribute *pCode_attr) {
    if (methods == NULL || methodIndex > methods_count) return false;

    char* pMI,*bc;
    pMI = bc = (char*)methods[methodIndex].pMethodInfoBase;
    /*
     * step over the pMethodInfoBase which size is 4
     * and attribute_name_index which size is 2
     */
    bc += 6;
    int nAttributes = getu2(bc);bc += 2;

    if (nAttributes > 0){
        for (int i = 0; i < nAttributes; ++i) {
            u2 name_index = getu2(bc);bc += 2;
            std::string strAttributeName;
            GetStringFormConstPool(name_index,strAttributeName);

            if (!strAttributeName.compare("Code")){
                char* ca = bc;
                pCode_attr->attribute_name_index = name_index;
                pCode_attr->attribute_length = getu4(ca);ca += 4;
                pCode_attr->max_stack = getu2(ca); ca += 2;
                pCode_attr->max_locals = getu2(ca); ca += 2;
                pCode_attr->code_length = getu4(ca); ca += 4;

                if(pCode_attr->code_length > 0){
                    pCode_attr->code = new u1[pCode_attr->code_length];
                    memcpy(pCode_attr->code,ca,pCode_attr->code_length);
                }else{
                    /*
                     * may be native code
                     */
                    pCode_attr->code = NULL;
                }

                ca += pCode_attr->code_length;
                /*
                 * after code is the exception
                 */
                pCode_attr->exception_table_length = getu2(ca); ca += 2;
                if (pCode_attr->exception_table_length > 0){
                    pCode_attr->exception_table = new Exception_table[pCode_attr->exception_table_length];

                    for (int j = 0; j < pCode_attr->exception_table_length; ++j) {
                        pCode_attr->exception_table[j].start_pc = getu2(ca);ca+=2;
                        pCode_attr->exception_table[j].end_pc = getu2(ca); ca += 2;
                        pCode_attr->exception_table[j].handler_pc = getu2(ca);ca+=2;
                        pCode_attr->exception_table[j].catch_type = getu2(ca);ca+=2;
                    }
                }
            }
            u4 len = getu4(bc);bc+=4;
            bc += len;
        }
    }

    return true;
}

int JavaClass::GetMethodIndex(std::string methodName, std::string methodDesc, JavaClass *&pClass) {
    if(methods == NULL) return false;

    JavaClass* pCurrentClass = this;
    while(pCurrentClass){
        for (int i = 0; i < pCurrentClass->methods_count; ++i) {
            std::string name,desc;

            pCurrentClass->GetStringFormConstPool(pCurrentClass->methods[i].name_index,name);
            if (name != methodName) continue;

            pCurrentClass->GetStringFormConstPool(pCurrentClass->methods[i].descriptor_index,desc);

            if (desc == methodDesc){
                if(pClass){
                    pClass = pCurrentClass;
                }

                return i;
            }
        }

        if (pClass != NULL){
            pCurrentClass = pCurrentClass->GetSuperClass();
        }else{
            break;
        }
    }
    return -1;
}

int JavaClass::GetFieldIndex(std::string strName, std::string &strDesc) {

    if(fields == NULL) return false;
    for (int i = 0; i < fields_count; ++i) {
        std::string name,desc;
        GetStringFormConstPool(fields[i].name_index,name);
        if (name != strName) continue;

        GetStringFormConstPool(fields[i].descriptor_index,desc);

        strDesc = desc;
        return i;
    }
    return -1;
}

/*
 * Object size should take super class into consideration
 */
u4 JavaClass::GetObjectSize() {
    u4 size = fields_count * sizeof(Variable);
    std::string superClassName = GetSuperClassName();
    JavaClass* pSuperClass = m_pClassHeap->GetClass(superClassName);
    u4 superObjectSize = 0;
    if (pSuperClass){
        superObjectSize = pSuperClass->GetObjectSize();
    }

    size += superObjectSize;

    return size;
}

/*
 * Field count also should take super class into consideration
 */
u4 JavaClass::GetObjectFieldCount() {
    u4 count = fields_count;
    std::string superClassName = GetSuperClassName();
    JavaClass* pSuperClass = m_pClassHeap->GetClass(superClassName);
    u4 superObjectFieldCount = 0;
    if(pSuperClass)
        superObjectFieldCount = pSuperClass->GetObjectFieldCount();

    count += superObjectFieldCount;
    return 0;
}

JavaClass* JavaClass::GetSuperClass() {
    std::string superClassName = GetSuperClassName();
    JavaClass* pSuperClass = m_pClassHeap->GetClass(superClassName);

    return pSuperClass;
}

bool JavaClass::CreateObject(u2 index, ObjectHeap *pObjectHeap, Object &object) {
    char* cp = (char*)this->constant_pool[index];
    assert(cp[0] == CONSTANT_Class);
    assert(pObjectHeap);

    u2 name_index = getu2(&cp[1]);
    std::string strClassName;
    if (!this->GetStringFormConstPool(name_index,strClassName))
        return false;

    JavaClass* pNewClass = this->m_pClassHeap->GetClass(strClassName);
    if(pNewClass == NULL) return false;
    object = pObjectHeap->CreateObject(pNewClass);
    return true;
}

bool JavaClass::CreateObjectArray(u2 index, u4 count, ObjectHeap *pObjectHeap, Object &object) {
    char* cp = (char*)this->constant_pool[index];
    assert(cp[0] == CONSTANT_Class);

    u2 name_index = getu2(&cp[1]);
    std::string strClassName;
    if(!this->GetStringFormConstPool(name_index,strClassName))
        return false;

    JavaClass* pNewClass = this->m_pClassHeap->GetClass(strClassName);
    if (pNewClass == NULL) return false;

    return pObjectHeap->CreateObjectArray(pNewClass,count,object);
}